//
//  DXMLImporter.m
//  DerbyStatter
//
//  Created by glenn andreas on 1/6/14.
//  Copyright (c) 2014 glenn andreas. All rights reserved.
//

#import "DXMLImporter.h"
#import "DXMLObject.h"


NSString *DXMLFileExtension = @"derbyxml";

@interface DXMLImporter() <NSXMLParserDelegate>
@property (strong) NSXMLParser *parser;
@property (strong) NSError *error;
@property (strong) DXMLObject *rootNode;
@property (strong) DXMLObject *currentNode;
@property (strong) NSMutableArray *nodeStack;
@property (assign) NSInteger stopAtDepth;
@property (assign) NSInteger currentDepth;
@end
@implementation DXMLImporter
- (id)initWithXMLParser: (NSXMLParser *) parser
{
    self = [super init];
    if (self) {
        _parser = parser;
    }
    return self;
}
- (id)initWithContentsOfURL:(NSURL *)url  // initializes the parser with the specified URL.
{
    return [self initWithXMLParser:[[NSXMLParser alloc] initWithContentsOfURL:url]];
}
- (id)initWithData:(NSData *)data; // create the parser from data
{
    return [self initWithXMLParser:[[NSXMLParser alloc] initWithData:data]];
}
- (id)initWithStream:(NSInputStream *)stream //create a parser that incrementally pulls data from the
{
    return [self initWithXMLParser:[[NSXMLParser alloc] initWithStream:stream]];
}
- (DXMLObject *) parseTopLevelOnly: (NSError **) error;
{
    self.stopAtDepth = 1;
    return [self parseToDepth:error];
}
- (DXMLObject *) parse:(NSError **)error
{
    self.stopAtDepth = 0;
    return [self parseToDepth: error];
}
- (DXMLObject *) parseToDepth: (NSInteger) depth error: (NSError **) error
{
    self.stopAtDepth = depth;
    return [self parseToDepth:error];
}

- (DXMLObject *) parseToDepth:(NSError **)error
{
    self.currentDepth = 0;
    if (self.rootNode) {
        return self.rootNode;
    }
    if (self.error) {
        if (error)
            *error = self.error;
        return nil;
    }
    BOOL parsed;
    [self.parser setShouldProcessNamespaces:NO];
    self.parser.delegate = self;
    @autoreleasepool {
        parsed = [self.parser parse];
    }
    if (parsed) {
        return self.rootNode;
    } else if (error) {
        *error = self.error;
    }
    return nil;
}

- (void)parserDidStartDocument:(NSXMLParser *)parser
// sent when the parser begins parsing of the document.
{
    self.nodeStack = [NSMutableArray array];
    // we don't actually record the document element
}
- (void)parserDidEndDocument:(NSXMLParser *)parser
// sent when the parser has completed parsing. If this is encountered, the parse was successful.
{
}
#ifdef notyet
// DTD handling methods for various declarations.
- (void)parser:(NSXMLParser *)parser foundNotationDeclarationWithName:(NSString *)name publicID:(NSString *)publicID systemID:(NSString *)systemID
{
    
}

- (void)parser:(NSXMLParser *)parser foundUnparsedEntityDeclarationWithName:(NSString *)name publicID:(NSString *)publicID systemID:(NSString *)systemID notationName:(NSString *)notationName
{
    
}

- (void)parser:(NSXMLParser *)parser foundAttributeDeclarationWithName:(NSString *)attributeName forElement:(NSString *)elementName type:(NSString *)type defaultValue:(NSString *)defaultValue
{
    
}

- (void)parser:(NSXMLParser *)parser foundElementDeclarationWithName:(NSString *)elementName model:(NSString *)model
{
    
}

- (void)parser:(NSXMLParser *)parser foundInternalEntityDeclarationWithName:(NSString *)name value:(NSString *)value
{
    
}

- (void)parser:(NSXMLParser *)parser foundExternalEntityDeclarationWithName:(NSString *)name publicID:(NSString *)publicID systemID:(NSString *)systemID
{
    
}
#endif

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
// sent when the parser finds an element start tag.
// In the case of the cvslog tag, the following is what the delegate receives:
//   elementName == cvslog, namespaceURI == http://xml.apple.com/cvslog, qualifiedName == cvslog
// In the case of the radar tag, the following is what's passed in:
//    elementName == radar, namespaceURI == http://xml.apple.com/radar, qualifiedName == radar:radar
// If namespace processing >isn't< on, the xmlns:radar="http://xml.apple.com/radar" is returned as an attribute pair, the elementName is 'radar:radar' and there is no qualifiedName.
{
    self.currentDepth++;
    if (self.stopAtDepth && self.currentDepth > self.stopAtDepth) {
        return;
    }
    DXMLObject *node = [[DXMLObject alloc] initWithElementName:elementName attributes:attributeDict];
    [self.currentNode addChild:node];
    [self.nodeStack addObject:node];
    self.currentNode = node;
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
// sent when an end tag is encountered. The various parameters are supplied as above.
{
    if (self.stopAtDepth && self.currentDepth > self.stopAtDepth) {
        self.currentDepth--;
        return;
    }
    self.currentDepth--;
    if (self.nodeStack.count == 1)
        self.rootNode = self.currentNode;
   [self.nodeStack removeLastObject];
    if (self.nodeStack.count)
        self.currentNode = self.nodeStack.lastObject;
    else
        self.currentNode = nil;
}
#ifdef notyet
- (void)parser:(NSXMLParser *)parser didStartMappingPrefix:(NSString *)prefix toURI:(NSString *)namespaceURI
// sent when the parser first sees a namespace attribute.
// In the case of the cvslog tag, before the didStartElement:, you'd get one of these with prefix == @"" and namespaceURI == @"http://xml.apple.com/cvslog" (i.e. the default namespace)
// In the case of the radar:radar tag, before the didStartElement: you'd get one of these with prefix == @"radar" and namespaceURI == @"http://xml.apple.com/radar"
{
    
}

- (void)parser:(NSXMLParser *)parser didEndMappingPrefix:(NSString *)prefix
// sent when the namespace prefix in question goes out of scope.
{
    
}
#endif

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string;
// This returns the string of the characters encountered thus far. You may not necessarily get the longest character run. The parser reserves the right to hand these to the delegate as potentially many calls in a row to -parser:foundCharacters:
{
    if (self.stopAtDepth && self.currentDepth > self.stopAtDepth) {
        return;
    }
    [self.currentNode appendText:string];
}

#ifdef notyet
- (void)parser:(NSXMLParser *)parser foundIgnorableWhitespace:(NSString *)whitespaceString;
// The parser reports ignorable whitespace in the same way as characters it's found.
{
    
}

- (void)parser:(NSXMLParser *)parser foundProcessingInstructionWithTarget:(NSString *)target data:(NSString *)data;
// The parser reports a processing instruction to you using this method. In the case above, target == @"xml-stylesheet" and data == @"type='text/css' href='cvslog.css'"
{
    
}

- (void)parser:(NSXMLParser *)parser foundComment:(NSString *)comment;
// A comment (Text in a <!-- --> block) is reported to the delegate as a single string
{
    
}

- (void)parser:(NSXMLParser *)parser foundCDATA:(NSData *)CDATABlock;
// this reports a CDATA block to the delegate as an NSData.
{
    
}
#endif

#ifdef notyet
- (NSData *)parser:(NSXMLParser *)parser resolveExternalEntityName:(NSString *)name systemID:(NSString *)systemID;
// this gives the delegate an opportunity to resolve an external entity itself and reply with the resulting data.
{
    
}
#endif
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError;
// ...and this reports a fatal error to the delegate. The parser will stop parsing.
{
    self.error = parseError;
}

- (void)parser:(NSXMLParser *)parser validationErrorOccurred:(NSError *)validationError;
{
    self.error = validationError;
}
@end
